# Application Basics ## Introduction This chapter provides end-to-end development guidance for Android application developers working with embedded hardware platforms (using the Quectel Pi smart main control board series as an example). It covers core operations including development environment setup, debugging tool usage, hardware-related API calls, and application deployment and startup. It aims to help developers quickly get started with embedded Android development, solve basic problems from environment configuration to application implementation, adapt to the special development requirements of embedded hardware, and lay a technical foundation for subsequent complex function development. ## Development Environment ### Preparation of Development Tools #### Download and Installation of Android Studio - **Official download address**: [Android Studio Official Website]() - **Installation process** - Download the corresponding installation package according to the operating system (Windows/macOS/Linux); - For Windows systems: Double-click the `.exe` installation package to launch the setup wizard; - For macOS systems: Mount the `.dmg` image and drag Android Studio to the Applications folder; - For Linux systems: Extract the `.tar.gz` package to the `/opt` directory, and execute `/opt/android-studio/bin/studio.sh` to start the installation. - Check the core components "**Android Studio**" and "**Android SDK**", select a non-system disk (it is recommended to reserve ≥20 GB of space) as the installation path, and complete the basic installation; - Select the "**Standard**" configuration for the first startup, and wait for the automatic download of the SDK and toolchain to complete. #### Embedded Hardware Driver Configuration For Quectel Pi series hardware, additional driver configuration is required: - Windows systems Download the official Quectel hardware driver package, double-click the installer, and complete the configuration according to the wizard. - Linux or macOS systems The system usually has a built-in universal USB driver, and the hardware can be automatically recognized after connection, generally no need to manually install additional drivers. - Driver verification: Connect the hardware to the computer, execute the `lsusb` command in the Windows Device Manager or Linux terminal. If the corresponding device can be recognized, the driver configuration is successful. #### NDK Configuration (for underlying hardware API calls) - Open Android Studio, go to `File > Settings > Appearance & Behavior > System Settings > Android SDK`, and switch to the `SDK Tools` tab. - Check `Show Package Details`, `NDK (Side by side)` and `CMake`, select the corresponding version and click "**Apply**" to complete the download and installation. - Configure the NDK path in the project's `local.properties` file: ``` ndk.dir=SDK installation path/ndk/corresponding version number ``` ### Project Environment Initialization - Create a new Android project, select the "**Empty Activity**" template, configure the project name and package name. It is recommended that the minimum compatible Android version be ≥ Android 10 (to adapt to embedded hardware). - Import related dependency packages such as the Quectel hardware SDK in the `build.gradle` (Module level) file (this step is optional). ## APK Development - **Launch Android Studio**: Click "New Project" to create a new project. When selecting the project template, it is recommended to choose the most concise **Empty Views Activity** template. ```{image} images/image_QvzLbQ9lwokUaWxZdJWcwiVnn5c.webp :width: 762px :height: 294px :align: center ``` - ***Note: If you select the "Empty Activity" template, a Compose UI project will be generated.*** ```{image} images/image_YsAbbuJR5oAts8x0dz0cf8MUnUe.webp :width: 888px :height: 636px :align: center ``` - **Select language**: Select the development language in this step. Even if Kotlin is selected, the project can still be developed using Java, and Android projects fully support mixed development of Java and Kotlin. ```{image} images/image_JMv9bLkFioyPIYxzcyBcKYnOnkf.webp :width: 870px :height: 470px ``` - **Download dependencies and SDK**: After completing the above steps, Android Studio will automatically open the project and download Gradle and required dependencies. If you are prompted "SDK missing" on the first startup, click "Next" to automatically download the Android SDK. Please confirm the SDK installation path and wait for the download to complete. The build time depends on the network conditions. - **Build and run**: After the Gradle build is completed, enter the main interface, and the system has generated the basic demo code. If there is no error and the green "Run" button at the top is available, the build is normal. Click this button to compile the project and start the emulator. In addition, you can also use the `adb connect` command to connect to a real device for running. ```{image} images/image_XFxWbSsVqoWvSMxsx53cTjpHn9d.webp :width: 1271px :height: 315px ``` - **Successful operation**: At this point, the application has successfully run on the emulator, displaying a simple welcome interface, and you can start development. ```{image} images/image_J4qebzXCloMKb1xqcgacFPeRnuc.webp :width: 1280px :height: 626px :align: center ``` ### Basic API Examples #### GPIO Control API (Core Function of Embedded Hardware) ##### Introduction to GPIO GPIO (General-Purpose Input/Output) is a fundamental and crucial concept in embedded systems and hardware development. On Android devices, it serves as a "bridge" between the system and the external physical world. GPIO control in embedded Android needs to be combined with hardware drivers. The following is a basic example based on system file operations (taking GPIO 12 as an example). ##### Permission Configuration Add hardware access permissions in `AndroidManifest.xml`: ``` ``` ##### Java Code Implementation Reading and writing files under /sys/class/gpio through Java code usually requires root permission. Therefore, performing these operations on a regular Android phone is very difficult, and this method is usually only applicable to devices that have obtained root permission or development boards designed specifically for developers. ``` import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; public class GpioManager { //GPIO operation base path private static final String GPIO_BASE_PATH = "/sys/class/gpio/"; private final int gpioNum; public GpioManager(int gpioNum) { this.gpioNum = gpioNum; exportGpio(); } private void exportGpio() { try { File exportFile = new File(GPIO_BASE_PATH + "export"); BufferedWriter writer = new BufferedWriter(new FileWriter(exportFile)); writer.write(String.valueOf(gpioNum)); writer.close(); //Default to output mode setGpioDirection("out"); } catch (IOException e) { e.printStackTrace(); } } /** * Set GPIO direction (in/out) * @param direction Direction parameter */ public void setGpioDirection(String direction) { try { File dirFile = new File(GPIO_BASE_PATH + "gpio" + gpioNum + "/direction"); BufferedWriter writer = new BufferedWriter(new FileWriter(dirFile)); writer.write(direction); writer.close(); } catch (IOException e) { e.printStackTrace(); } } /** * Control GPIO level (high level 1/low level 0) * @param value Level value */ public void setGpioValue(int value) { try { File valueFile = new File(GPIO_BASE_PATH + "gpio" + gpioNum + "/value"); BufferedWriter writer = new BufferedWriter(new FileWriter(valueFile)); writer.write(String.valueOf(value)); writer.close(); } catch (IOException e) { e.printStackTrace(); } } /** * Release GPIO pin */ public void unexportGpio() { try { File unexportFile = new File(GPIO_BASE_PATH + "unexport"); BufferedWriter writer = new BufferedWriter(new FileWriter(unexportFile)); writer.write(String.valueOf(gpioNum)); writer.close(); } catch (IOException e) { e.printStackTrace(); } } } ``` ##### Calling Example ``` //Initialize GPIO 12 pin GpioManager gpioManager = new GpioManager(12); //Set GPIO to high level gpioManager.setGpioValue(1); //Release GPIO after business logic is executed gpioManager.unexportGpio(); ``` #### NDK Code Implementation Since the Java/Kotlin code of a regular application runs at the Android application layer and cannot directly operate hardware, it is necessary to bridge through JNI (Java Native Interface). Call the Native layer code written in C/C++ to implement GPIO control (read/write /sys/class/gpio files or directly operate /dev/mem registers), and compile the C/C++ code into a .so dynamic link library for the Java layer to call through NDK. ##### JNI Layer Code Example ``` #include #include #include #include #include #define GPIO_BASE_PATH "/sys/class/gpio/" //Export GPIO pin static int gpio_export(int pin) { char buffer[64]; int fd = open(GPIO_BASE_PATH "export", O_WRONLY); if (fd < 0) { perror("Failed to open export file"); return -1; } snprintf(buffer, sizeof(buffer), "%d", pin); write(fd, buffer, strlen(buffer)); close(fd); return 0; } //Release GPIO pin static int gpio_unexport(int pin) { char buffer[64]; int fd = open(GPIO_BASE_PATH "unexport", O_WRONLY); if (fd < 0) { perror("Failed to open unexport file"); return -1; } snprintf(buffer, sizeof(buffer), "%d", pin); write(fd, buffer, strlen(buffer)); close(fd); return 0; } //Set GPIO direction (in/out) static int gpio_set_direction(int pin, const char *dir) { char buffer[64]; snprintf(buffer, sizeof(buffer), GPIO_BASE_PATH "gpio%d/direction", pin); int fd = open(buffer, O_WRONLY); if (fd < 0) { perror("Failed to open direction file"); return -1; } write(fd, dir, strlen(dir)); close(fd); return 0; } //Set GPIO level (1/0) static int gpio_set_value(int pin, int value) { char buffer[64]; snprintf(buffer, sizeof(buffer), GPIO_BASE_PATH "gpio%d/value", pin); int fd = open(buffer, O_WRONLY); if (fd < 0) { perror("Failed to open value file"); return -1; } char val_str[2] = {value + '0', '\0'}; write(fd, val_str, strlen(val_str)); close(fd); return 0; } //JNI method: Initialize GPIO JNIEXPORT jint JNICALL Java_com_example_gpiocontrol_MainActivity_gpioInit(JNIEnv *env, jobject thiz, jint pin) { if (gpio_export(pin) < 0) return -1; if (gpio_set_direction(pin, "out") < 0) return -1; return 0; } //JNI method: Set GPIO level JNIEXPORT jint JNICALL Java_com_example_gpiocontrol_MainActivity_gpioSetValue(JNIEnv *env, jobject thiz, jint pin, jint value) { return gpio_set_value(pin, value); } //JNI method: Release GPIO JNIEXPORT jint JNICALL Java_com_example_gpiocontrol_MainActivity_gpioRelease(JNIEnv *env, jobject thiz, jint pin) { return gpio_unexport(pin); } ``` ##### CMakeLists Configuration ``` cmake_minimum_required(VERSION 3.22.1) project("gpiocontrol") # Add shared library add_library( gpiocontrol SHARED gpio_control.c) # Link system library target_link_libraries( gpiocontrol log) ``` ##### Java Layer Code Call ``` package com.example.gpiocontrol; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { //Load Native library static { System.loadLibrary("gpiocontrol"); } //Declare native methods private native int gpioInit(int pin); private native int gpioSetValue(int pin, int value); private native int gpioRelease(int pin); private static final int GPIO_PIN = 12; //Controlled GPIO pin @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Initialize GPIO gpioInit(GPIO_PIN); //High level button Button btnHigh = findViewById(R.id.btn_high); btnHigh.setOnClickListener(v -> gpioSetValue(GPIO_PIN, 1)); //Low level button Button btnLow = findViewById(R.id.btn_low); btnLow.setOnClickListener(v -> gpioSetValue(GPIO_PIN, 0)); } @Override protected void onDestroy() { super.onDestroy(); //Release GPIO gpioRelease(GPIO_PIN); } } ``` #### Android App Directly Calling Shell (Runtime.exec) Execute Shell commands through Runtime.getRuntime().exec() in Java/Kotlin code, read and write GPIO control files in the /sys/class/gpio directory with su permission, and implement level control. This method requires the development board to have Root permission. ##### Core Code Example ``` //Initialize GPIO (export pin + set output direction) try { execShellCommand("su -c echo " + GPIO_PIN + " > /sys/class/gpio/export"); execShellCommand("su -c echo out > /sys/class/gpio/gpio" + GPIO_PIN + "/direction"); } catch (IOException e) { e.printStackTrace(); } //High level button Button btnHigh = findViewById(R.id.btn_high); btnHigh.setOnClickListener(v -> { try { execShellCommand("su -c echo 1 > /sys/class/gpio/gpio" + GPIO_PIN + "/value"); } catch (IOException e) { e.printStackTrace(); } }); //Low level button Button btnLow = findViewById(R.id.btn_low); btnLow.setOnClickListener(v -> { try { execShellCommand("su -c echo 0 > /sys/class/gpio/gpio" + GPIO_PIN + "/value"); } catch (IOException e) { e.printStackTrace(); } }); /** * Execute Shell command */ private void execShellCommand(String command) throws IOException { Runtime.getRuntime().exec(command); } ``` #### Vendor-Provided Dedicated API or Jar Package Most vendors will encapsulate standardized Java APIs (provided in the form of Jar packages or SDKs) based on underlying drivers. Developers can obtain the corresponding SDK from the vendor and integrate it into the Android application, and then directly call the encapsulated API to control GPIO without paying attention to the specific implementation of underlying hardware operations. ### Camera API (Based on Camera2 Native API) The following is an implementation example based on the Android native Camera2 API, adapted to the single-camera scenario of embedded devices, supporting preview and photo taking functions. #### Dependency Configuration Camera2 is an Android native API and does not require third-party dependencies. It is only necessary to confirm in `build.gradle` (Module level) that the minimum compatible version is not lower than Android 5.0 (API 21). #### Permission and Layout Configuration - **Permission declaration** (AndroidManifest.xml): ``` <-- Camera hardware features --> ``` - **Layout file** (activity_camera2.xml): Use `TextureView` as the preview carrier, adapted to the screen size of embedded devices: ```